1use std::collections::HashMap;
10use std::future::Future;
11use std::pin::Pin;
12use std::sync::atomic::{AtomicBool, Ordering};
13use std::sync::{Arc, Mutex};
14
15use tokio::sync::mpsc;
16use uuid::Uuid;
17
18use super::super::rpc_types::{RpcContext, RpcMessage, RpcOpts, COMMAND_EVENT_RECV};
19
20pub const DEFAULT_TIMEOUT_MS: i64 = 5000;
23const RESP_CH_SIZE: usize = 32;
24
25pub type HandlerResult = Result<Option<serde_json::Value>, String>;
29
30pub type CommandHandler = Box<
36 dyn Fn(serde_json::Value, RpcContext) -> Pin<Box<dyn Future<Output = HandlerResult> + Send>>
37 + Send
38 + Sync,
39>;
40
41pub type StreamHandler = Box<
43 dyn Fn(
44 serde_json::Value,
45 RpcContext,
46 )
47 -> Pin<Box<dyn Future<Output = Result<mpsc::Receiver<HandlerResult>, String>> + Send>>
48 + Send
49 + Sync,
50>;
51
52enum Handler {
53 Call(CommandHandler),
54 #[allow(dead_code)]
55 Stream(StreamHandler),
56}
57
58pub struct RpcResponseHandler {
63 engine: Arc<WshRpcEngine>,
64 req_id: String,
65 #[allow(dead_code)]
66 source: String,
67 canceled: AtomicBool,
68 done: AtomicBool,
69}
70
71impl RpcResponseHandler {
72 pub fn send_response(&self, data: Option<serde_json::Value>, done: bool) {
75 if self.done.load(Ordering::Relaxed) {
76 return;
77 }
78 let msg = RpcMessage {
79 resid: self.req_id.clone(),
80 data,
81 cont: !done,
82 ..Default::default()
83 };
84 if done {
85 self.done.store(true, Ordering::Relaxed);
86 }
87 self.engine.send_output(msg);
88 }
89
90 pub fn send_error(&self, err: &str) {
92 if self.done.load(Ordering::Relaxed) {
93 return;
94 }
95 self.done.store(true, Ordering::Relaxed);
96 let msg = RpcMessage {
97 resid: self.req_id.clone(),
98 error: err.to_string(),
99 ..Default::default()
100 };
101 self.engine.send_output(msg);
102 }
103
104 #[allow(dead_code)]
106 pub fn is_canceled(&self) -> bool {
107 self.canceled.load(Ordering::Relaxed)
108 }
109
110 #[allow(dead_code)]
112 pub fn get_source(&self) -> &str {
113 &self.source
114 }
115
116 fn cancel(&self) {
118 self.canceled.store(true, Ordering::Relaxed);
119 }
120
121 fn finalize(&self) {
123 if self.done.load(Ordering::Relaxed) {
124 return;
125 }
126 self.send_response(None, true);
127 }
128}
129
130#[allow(dead_code)]
135pub struct RpcRequestHandler {
136 req_id: String,
137 resp_rx: mpsc::Receiver<RpcMessage>,
138 last_was_cont: bool,
139}
140
141impl RpcRequestHandler {
142 #[allow(dead_code)]
144 pub async fn next_response(&mut self) -> Option<Result<serde_json::Value, String>> {
145 if !self.last_was_cont && self.req_id.is_empty() {
146 return None;
147 }
148 match self.resp_rx.recv().await {
149 Some(msg) => {
150 self.last_was_cont = msg.cont;
151 if !msg.error.is_empty() {
152 Some(Err(msg.error))
153 } else {
154 Some(Ok(msg.data.unwrap_or(serde_json::Value::Null)))
155 }
156 }
157 None => None,
158 }
159 }
160
161 #[allow(dead_code)]
163 pub fn is_done(&self) -> bool {
164 !self.last_was_cont
165 }
166
167 #[allow(dead_code)]
169 pub fn req_id(&self) -> &str {
170 &self.req_id
171 }
172}
173
174struct EngineInner {
177 handlers: HashMap<String, Handler>,
178 pending_responses: HashMap<String, mpsc::Sender<RpcMessage>>,
179 active_handlers: HashMap<String, Arc<RpcResponseHandler>>,
180 #[allow(dead_code)]
181 auth_token: String,
182 rpc_context: Option<RpcContext>,
183}
184
185pub struct WshRpcEngine {
190 inner: Mutex<EngineInner>,
191 output_tx: mpsc::UnboundedSender<RpcMessage>,
192}
193
194impl WshRpcEngine {
195 fn lock_inner(&self) -> std::sync::MutexGuard<'_, EngineInner> {
200 self.inner.lock().unwrap_or_else(|e| e.into_inner())
201 }
202
203 pub fn new() -> (Arc<Self>, mpsc::UnboundedReceiver<RpcMessage>) {
206 let (output_tx, output_rx) = mpsc::unbounded_channel();
207 let engine = Arc::new(Self {
208 inner: Mutex::new(EngineInner {
209 handlers: HashMap::new(),
210 pending_responses: HashMap::new(),
211 active_handlers: HashMap::new(),
212 auth_token: String::new(),
213 rpc_context: None,
214 }),
215 output_tx,
216 });
217 (engine, output_rx)
218 }
219
220 pub fn register_handler(&self, command: &str, handler: CommandHandler) {
222 let mut inner = self.lock_inner();
223 inner
224 .handlers
225 .insert(command.to_string(), Handler::Call(handler));
226 }
227
228 #[allow(dead_code)]
230 pub fn register_stream_handler(&self, command: &str, handler: StreamHandler) {
231 let mut inner = self.lock_inner();
232 inner
233 .handlers
234 .insert(command.to_string(), Handler::Stream(handler));
235 }
236
237 #[allow(dead_code)]
239 pub fn set_auth_token(&self, token: &str) {
240 let mut inner = self.lock_inner();
241 inner.auth_token = token.to_string();
242 }
243
244 #[allow(dead_code)]
246 pub fn get_auth_token(&self) -> String {
247 let inner = self.lock_inner();
248 inner.auth_token.clone()
249 }
250
251 #[allow(dead_code)]
253 pub fn set_rpc_context(&self, ctx: RpcContext) {
254 let mut inner = self.lock_inner();
255 inner.rpc_context = Some(ctx);
256 }
257
258 pub fn handle_message(self: &Arc<Self>, msg: RpcMessage) {
260 if msg.cancel {
262 if !msg.reqid.is_empty() {
263 self.handle_cancel_request(&msg.reqid);
264 }
265 return;
266 }
267
268 if msg.command == COMMAND_EVENT_RECV {
270 return;
272 }
273
274 if !msg.command.is_empty() {
276 let engine = self.clone();
277 tokio::spawn(async move {
278 engine.handle_request(msg).await;
279 });
280 return;
281 }
282
283 if !msg.resid.is_empty() {
285 self.handle_response(msg);
286 }
287 }
288
289 #[allow(dead_code)]
291 pub async fn send_command(
292 self: &Arc<Self>,
293 command: &str,
294 data: serde_json::Value,
295 opts: &RpcOpts,
296 ) -> Result<serde_json::Value, String> {
297 let mut handler = self.send_request(command, data, opts)?;
298 match handler.next_response().await {
299 Some(result) => result,
300 None => Err("no response received".to_string()),
301 }
302 }
303
304 #[allow(dead_code)]
306 pub fn send_request(
307 self: &Arc<Self>,
308 command: &str,
309 data: serde_json::Value,
310 opts: &RpcOpts,
311 ) -> Result<RpcRequestHandler, String> {
312 let req_id = Uuid::new_v4().to_string();
313 let (resp_tx, resp_rx) = mpsc::channel(RESP_CH_SIZE);
314
315 {
316 let mut inner = self.lock_inner();
317 inner
318 .pending_responses
319 .insert(req_id.clone(), resp_tx);
320 }
321
322 let timeout = if opts.timeout > 0 {
323 opts.timeout
324 } else {
325 DEFAULT_TIMEOUT_MS
326 };
327 let route = if opts.route.is_empty() {
328 String::new()
329 } else {
330 opts.route.clone()
331 };
332
333 let msg = RpcMessage {
334 command: command.to_string(),
335 reqid: req_id.clone(),
336 timeout,
337 route,
338 data: Some(data),
339 authtoken: self.get_auth_token(),
340 ..Default::default()
341 };
342 self.send_output(msg);
343
344 Ok(RpcRequestHandler {
345 req_id,
346 resp_rx,
347 last_was_cont: true, })
349 }
350
351 #[allow(dead_code)]
353 pub fn send_command_no_response(
354 &self,
355 command: &str,
356 data: serde_json::Value,
357 route: &str,
358 ) {
359 let msg = RpcMessage {
360 command: command.to_string(),
361 data: Some(data),
362 route: route.to_string(),
363 authtoken: self.get_auth_token(),
364 ..Default::default()
365 };
366 self.send_output(msg);
367 }
368
369 fn send_output(&self, msg: RpcMessage) {
372 if self.output_tx.send(msg).is_err() {
373 tracing::warn!("[rpc-engine] output channel closed — message dropped");
374 }
375 }
376
377 async fn handle_request(self: Arc<Self>, msg: RpcMessage) {
378 let request_start = std::time::Instant::now();
379 let timeout_ms = if msg.timeout > 0 {
380 msg.timeout
381 } else {
382 DEFAULT_TIMEOUT_MS
383 };
384
385 let handler = Arc::new(RpcResponseHandler {
386 engine: self.clone(),
387 req_id: msg.reqid.clone(),
388 source: msg.source.clone(),
389 canceled: AtomicBool::new(false),
390 done: AtomicBool::new(false),
391 });
392
393 if !msg.reqid.is_empty() {
395 let mut inner = self.lock_inner();
396 inner
397 .active_handlers
398 .insert(msg.reqid.clone(), handler.clone());
399 }
400
401 let rpc_context = {
402 let inner = self.lock_inner();
403 inner.rpc_context.clone().unwrap_or_default()
404 };
405
406 let data = msg.data.unwrap_or(serde_json::Value::Null);
407 let command = msg.command.clone();
408
409 let has_call;
411 let has_stream;
412 {
413 let inner = self.lock_inner();
414 match inner.handlers.get(&command) {
415 Some(Handler::Call(_)) => {
416 has_call = true;
417 has_stream = false;
418 }
419 Some(Handler::Stream(_)) => {
420 has_call = false;
421 has_stream = true;
422 }
423 None => {
424 has_call = false;
425 has_stream = false;
426 }
427 }
428 }
429
430 let dispatch_elapsed = request_start.elapsed();
431
432 if !has_call && !has_stream {
433 handler.send_error(&format!("unknown command: {}", command));
434 self.cleanup_handler(&msg.reqid);
435 return;
436 }
437
438 let timeout_dur = std::time::Duration::from_millis(timeout_ms as u64);
439
440 if has_call {
441 let handler_start = std::time::Instant::now();
444 let fut = {
445 let inner = self.lock_inner();
446 match inner.handlers.get(&command) {
447 Some(Handler::Call(h)) => h(data.clone(), rpc_context.clone()),
448 _ => Box::pin(async { Err("handler disappeared".to_string()) }),
449 }
450 };
451 let result = tokio::time::timeout(timeout_dur, fut).await;
452 let handler_elapsed = handler_start.elapsed();
453 let total_elapsed = request_start.elapsed();
454
455 tracing::info!(
456 "[rpc-perf] command={} dispatch={:.2}ms handler={:.2}ms total={:.2}ms",
457 command,
458 dispatch_elapsed.as_secs_f64() * 1000.0,
459 handler_elapsed.as_secs_f64() * 1000.0,
460 total_elapsed.as_secs_f64() * 1000.0,
461 );
462
463 match result {
464 Ok(Ok(resp_data)) => handler.send_response(resp_data, true),
465 Ok(Err(err)) => handler.send_error(&err),
466 Err(_) => handler.send_error(&format!("EC-TIME: timeout ({}ms)", timeout_ms)),
467 }
468 } else {
469 let fut = {
471 let inner = self.lock_inner();
472 match inner.handlers.get(&command) {
473 Some(Handler::Stream(h)) => h(data.clone(), rpc_context.clone()),
474 _ => Box::pin(async { Err("handler disappeared".to_string()) }),
475 }
476 };
477 let stream_result = tokio::time::timeout(timeout_dur, fut).await;
478
479 match stream_result {
480 Ok(Ok(mut rx)) => {
481 loop {
483 match tokio::time::timeout(timeout_dur, rx.recv()).await {
484 Ok(Some(Ok(resp_data))) => {
485 handler.send_response(resp_data, false);
486 }
487 Ok(Some(Err(err))) => {
488 handler.send_error(&err);
489 break;
490 }
491 Ok(None) => {
492 handler.finalize();
494 break;
495 }
496 Err(_) => {
497 handler.send_error(&format!(
498 "EC-TIME: stream timeout ({}ms)",
499 timeout_ms
500 ));
501 break;
502 }
503 }
504 }
505 }
506 Ok(Err(err)) => handler.send_error(&err),
507 Err(_) => {
508 handler.send_error(&format!("EC-TIME: timeout ({}ms)", timeout_ms))
509 }
510 }
511 }
512
513 self.cleanup_handler(&msg.reqid);
514 }
515
516 fn handle_response(&self, msg: RpcMessage) {
517 let inner = self.lock_inner();
518 if let Some(tx) = inner.pending_responses.get(&msg.resid) {
519 let is_done = !msg.cont;
520 if tx.try_send(msg.clone()).is_err() {
521 tracing::warn!(resid = %msg.resid, "[rpc-engine] response channel full/closed — reply dropped");
522 }
523 if is_done {
524 drop(inner);
525 let mut inner = self.lock_inner();
526 inner.pending_responses.remove(&msg.resid);
527 }
528 }
529 }
530
531 fn handle_cancel_request(&self, req_id: &str) {
532 let inner = self.lock_inner();
533 if let Some(handler) = inner.active_handlers.get(req_id) {
534 handler.cancel();
535 }
536 }
537
538 fn cleanup_handler(&self, req_id: &str) {
539 if req_id.is_empty() {
540 return;
541 }
542 let mut inner = self.lock_inner();
543 inner.active_handlers.remove(req_id);
544 }
545}
546
547#[cfg(test)]
552mod tests {
553 use super::*;
554
555 #[tokio::test]
556 async fn test_register_and_call_handler() {
557 let (engine, mut output_rx) = WshRpcEngine::new();
558
559 engine.register_handler(
560 "echo",
561 Box::new(|data, _ctx| {
562 Box::pin(async move { Ok(Some(data)) })
563 }),
564 );
565
566 let msg = RpcMessage {
567 command: "echo".to_string(),
568 reqid: "req-1".to_string(),
569 data: Some(serde_json::json!({"hello": "world"})),
570 ..Default::default()
571 };
572 engine.handle_message(msg);
573
574 let resp = tokio::time::timeout(
576 std::time::Duration::from_secs(1),
577 output_rx.recv(),
578 )
579 .await
580 .unwrap()
581 .unwrap();
582
583 assert_eq!(resp.resid, "req-1");
584 assert!(!resp.cont);
585 assert_eq!(resp.data, Some(serde_json::json!({"hello": "world"})));
586 }
587
588 #[tokio::test]
589 async fn test_unknown_command_returns_error() {
590 let (engine, mut output_rx) = WshRpcEngine::new();
591
592 let msg = RpcMessage {
593 command: "nonexistent".to_string(),
594 reqid: "req-2".to_string(),
595 ..Default::default()
596 };
597 engine.handle_message(msg);
598
599 let resp = tokio::time::timeout(
600 std::time::Duration::from_secs(1),
601 output_rx.recv(),
602 )
603 .await
604 .unwrap()
605 .unwrap();
606
607 assert_eq!(resp.resid, "req-2");
608 assert!(resp.error.contains("unknown command"));
609 }
610
611 #[tokio::test]
612 async fn test_handler_error_returns_error_response() {
613 let (engine, mut output_rx) = WshRpcEngine::new();
614
615 engine.register_handler(
616 "failme",
617 Box::new(|_data, _ctx| {
618 Box::pin(async move { Err("something went wrong".to_string()) })
619 }),
620 );
621
622 let msg = RpcMessage {
623 command: "failme".to_string(),
624 reqid: "req-3".to_string(),
625 ..Default::default()
626 };
627 engine.handle_message(msg);
628
629 let resp = tokio::time::timeout(
630 std::time::Duration::from_secs(1),
631 output_rx.recv(),
632 )
633 .await
634 .unwrap()
635 .unwrap();
636
637 assert_eq!(resp.error, "something went wrong");
638 }
639
640 #[tokio::test]
641 async fn test_send_command_roundtrip() {
642 let (engine, mut output_rx) = WshRpcEngine::new();
643
644 let engine_clone = engine.clone();
646 tokio::spawn(async move {
647 if let Some(msg) = output_rx.recv().await {
648 let resp = RpcMessage {
650 resid: msg.reqid.clone(),
651 data: msg.data.clone(),
652 ..Default::default()
653 };
654 engine_clone.handle_message(resp);
655 }
656 });
657
658 let opts = RpcOpts {
659 timeout: 1000,
660 ..Default::default()
661 };
662 let result = engine
663 .send_command("test", serde_json::json!(42), &opts)
664 .await;
665
666 assert!(result.is_ok());
667 assert_eq!(result.unwrap(), serde_json::json!(42));
668 }
669
670 #[tokio::test]
671 async fn test_stream_handler() {
672 let (engine, mut output_rx) = WshRpcEngine::new();
673
674 engine.register_stream_handler(
675 "counter",
676 Box::new(|_data, _ctx| {
677 Box::pin(async move {
678 let (tx, rx) = mpsc::channel(8);
679 tokio::spawn(async move {
680 for i in 0..3 {
681 let _ = tx.send(Ok(Some(serde_json::json!(i)))).await;
682 }
683 });
685 Ok(rx)
686 })
687 }),
688 );
689
690 let msg = RpcMessage {
691 command: "counter".to_string(),
692 reqid: "req-stream".to_string(),
693 ..Default::default()
694 };
695 engine.handle_message(msg);
696
697 let mut responses = Vec::new();
699 for _ in 0..4 {
700 match tokio::time::timeout(
702 std::time::Duration::from_secs(2),
703 output_rx.recv(),
704 )
705 .await
706 {
707 Ok(Some(resp)) => responses.push(resp),
708 _ => break,
709 }
710 }
711
712 assert!(responses.len() >= 3);
714 for resp in &responses[..3] {
716 assert!(resp.cont);
717 }
718 if responses.len() == 4 {
720 assert!(!responses[3].cont);
721 }
722 }
723
724 #[tokio::test]
725 async fn test_cancel_request() {
726 let (engine, mut output_rx) = WshRpcEngine::new();
727
728 let (started_tx, started_rx) = tokio::sync::oneshot::channel::<()>();
729 engine.register_handler(
730 "slow",
731 Box::new(move |_data, _ctx| {
732 Box::pin(async move {
733 tokio::time::sleep(std::time::Duration::from_secs(10)).await;
736 Ok(Some(serde_json::json!("done")))
737 })
738 }),
739 );
740
741 let msg = RpcMessage {
743 command: "slow".to_string(),
744 reqid: "req-cancel".to_string(),
745 timeout: 10000,
746 ..Default::default()
747 };
748 engine.handle_message(msg);
749
750 tokio::time::sleep(std::time::Duration::from_millis(50)).await;
752 let cancel_msg = RpcMessage {
753 cancel: true,
754 reqid: "req-cancel".to_string(),
755 ..Default::default()
756 };
757 engine.handle_message(cancel_msg);
758
759 let resp = tokio::time::timeout(
762 std::time::Duration::from_secs(12),
763 output_rx.recv(),
764 )
765 .await;
766 assert!(resp.is_ok());
767 drop(started_tx);
769 drop(started_rx);
770 }
771
772 #[tokio::test]
773 async fn test_send_command_no_response() {
774 let (engine, mut output_rx) = WshRpcEngine::new();
775
776 engine.send_command_no_response("notify", serde_json::json!({"msg": "hi"}), "");
777
778 let msg = tokio::time::timeout(
779 std::time::Duration::from_millis(100),
780 output_rx.recv(),
781 )
782 .await
783 .unwrap()
784 .unwrap();
785
786 assert_eq!(msg.command, "notify");
787 assert!(msg.reqid.is_empty());
788 }
789
790 #[tokio::test]
791 async fn test_auth_token() {
792 let (engine, _output_rx) = WshRpcEngine::new();
793 assert!(engine.get_auth_token().is_empty());
794
795 engine.set_auth_token("my-secret-token");
796 assert_eq!(engine.get_auth_token(), "my-secret-token");
797 }
798
799 #[tokio::test]
800 async fn test_rpc_context() {
801 let (engine, _output_rx) = WshRpcEngine::new();
802
803 let ctx = RpcContext {
804 client_type: "connserver".to_string(),
805 blockid: "blk-1".to_string(),
806 ..Default::default()
807 };
808 engine.set_rpc_context(ctx);
809
810 engine.register_handler(
812 "checkctx",
813 Box::new(|_data, ctx| {
814 Box::pin(async move {
815 Ok(Some(serde_json::json!({
816 "ctype": ctx.client_type,
817 "blockid": ctx.blockid,
818 })))
819 })
820 }),
821 );
822
823 let msg = RpcMessage {
824 command: "checkctx".to_string(),
825 reqid: "req-ctx".to_string(),
826 ..Default::default()
827 };
828 engine.handle_message(msg);
829
830 }
833}